﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;

namespace FRW.Common.Extensions
{
    /// <summary>
    /// This is a generic list that has added the events:
    /// ItemRemove, ItemAdded, ItemsCleared, ItemsAdded, BeforeItemAdded, and BeforeItemRemoved
    /// that was not added by default.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class EventList<T> : IList<T>, IList
    {
        #region Members
        private List<T> _items = null;
        private bool _disableEvents = false;

        #endregion

        #region Events

        /// <summary>
        /// Raises event when an item is added to the list.
        /// </summary>
        public event EventHandler<ListEventArgs<T>> ItemAdded;
        
        /// <summary>
        /// Raises event when an item is removed from the list.
        /// </summary>
        public event EventHandler<EventArgs> ItemRemoved;
        
        /// <summary>
        /// Raises event when the items are cleared from the list.
        /// </summary>
        public event EventHandler<EventArgs> ItemsCleared;
        
        /// <summary>
        /// Raises event when a collection of items have been added to the list
        /// </summary>
        public event EventHandler<EventArgs> ItemsAdded;
        
        /// <summary>
        /// Raises event before an item is added to the list.
        /// </summary>
        public event EventHandler<ListPreEventArgs<T>> BeforeItemAdded;

        /// <summary>
        /// Raises event before items are added to the list.
        /// </summary>
        public event EventHandler<ListPreEventArgs<T>> BeforeItemsAdded;

        /// <summary>
        /// Raises event before an item is removed from the list.
        /// </summary>
        public event EventHandler<ListPreEventArgs<T>> BeforeItemRemoved;
        
        #endregion

        #region Properties
        /// <summary>
        /// Returns the list of items in the class.
        /// </summary>
        protected List<T> Items
        {
            get { return this._items; }
            private set { this._items = value; }
        }

        /// <summary>
        /// 
        /// </summary>
        public bool DisableEvents
        {
            get { return _disableEvents; }
            set { _disableEvents = value; }
        }

        #endregion

        #region Constructors
        /// <summary>
        /// Default constructor
        /// </summary>
        public EventList()
            : this(0)
        {
            // Nothing to do by default
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="capacity"></param>
        public EventList(int capacity)
        {
            this.Items = new List<T>(capacity);
        }

        #endregion

        #region IList

        /// <summary>
        /// Gets or sets the element at the specified index
        /// </summary>
        /// 
        /// <param name ="index">The zero-based index of the element to get or set</param>
        /// 
        /// <returns>The element at the specified index</returns>
        /// 
        /// <exception cref="System.ArgumentOutOfRangeException">index is not a valid index in the System.Collections.Generic.IList</exception>
        /// <exception cref="System.NotSupportedException">The property is set and the System.Collections.Generic.IList<T> is read-only.</exception>
        public T this[int index]
        {
            get { return this.Items[index]; }
            set { this.Items[index] = value; }
        }

        /// <summary>
        /// Determines the index of a specific item in the System.Collections.Generic.IList<T>
        /// </summary>
        ///
        /// <param name="item">The object to locate in the System.Collections.Generic.IList<T></param>
        ///
        /// <returns>The index of item if found in the list; otherwise, -1</returns>
        public int IndexOf(T item)
        {
            return this.Items.IndexOf(item);
        }
        
        /// <summary>
        /// 
        /// </summary>
        /// <param name="index"></param>
        /// <param name="item"></param>
        public void Insert(int index, T item)
        {
            ListPreEventArgs<T> beforeEventArgs = new ListPreEventArgs<T>(item);

            OnBeforeItemAdded(this, beforeEventArgs);
            if (beforeEventArgs.Cancel != true)
            {
                this.Items.Insert(index, item);
                OnItemAdded(this, new ListEventArgs<T>(item));
            }
        }
      
        /// <summary>
        /// Removes the item at the specified index.
        /// </summary>
        /// <param name="index"></param>
        public void RemoveAt(int index)
        {
            ListPreEventArgs<T> beforeEventArgs = new ListPreEventArgs<T>(this.Items[index]);

            OnBeforeItemRemoved(this, beforeEventArgs);
            if (beforeEventArgs.Cancel != true)
            {
                this.Items.RemoveAt(index);
                OnItemRemoved(this, new EventArgs());
            }
        }
        #endregion

        /// <summary>
        /// Find the first matching element of the list to the supplied predicate
        /// </summary>
        /// <param name="condition">predicate to check the list against</param>
        /// <returns>First item matching condition or default for item type if no match</returns>
        public T Find(Predicate<T> condition)
        {
            foreach (T item in _items)
            {
                if (condition(item))
                {
                    return item;
                }
            }

            return default(T);
        }

        /// <summary>
        /// Find the first matching element of the list to the supplied predicate
        /// </summary>
        /// <param name="condition">predicate to check the list against</param>
        /// <returns>true if a match was found if not false</returns>
        public bool Exists(Predicate<T> condition)
        {
            return _items.Exists(condition);
        }


        #region ICollection Methods and Properties

        /// <summary>
        /// Return the count of items in the collection
        /// </summary>
        public int Count { get { return this.Items.Count; } }

        /// <summary>
        /// Gets a value indicating whether the collection is read only
        /// </summary>
        public bool IsReadOnly { get { return false; } }

        /// <summary>
        /// Adds an item to the collection
        /// </summary>
        /// <param name="item">The item to add to collection</param>
        public void Add(T item)
        {
            ListPreEventArgs<T> beforeEventArgs = new ListPreEventArgs<T>(item);

            OnBeforeItemAdded(this, beforeEventArgs);
            if (beforeEventArgs.Cancel != true)
            {
                this.Items.Add(item);
                OnItemAdded(this, new ListEventArgs<T>(item));
            }
        }

        /// <summary>
        /// Add a range of elements into the list.
        /// </summary>
        /// <param name="range">Enumerable collection of items to add.</param>
        public void AddRange(IEnumerable<T> range)
        {
            // we need a dummy item as passing an element in the pre event cannot be achieved when adding multiple items
            T dummyItem = default(T);

            ListPreEventArgs<T> beforeEventArgs = new ListPreEventArgs<T>(dummyItem);

            OnBeforeItemsAdded(this, beforeEventArgs);
            if (beforeEventArgs.Cancel != true)
            {
                foreach (T item in range)
                {
                    Add(item);
                }

                OnItemsAdded(this, new EventArgs());
            }
        }

        /// <summary>
        /// Removes all items from the collection
        /// </summary>
        public void Clear()
        {
            this.Items.Clear();
            OnItemsCleared(this, new EventArgs());
        }

        /// <summary>
        /// Determines whether the collection contains a specific value.
        /// </summary>
        /// <param name="item">The object to locate in the collection</param>
        /// <returns>true if item is found in the collection</returns>
        public bool Contains(T item)
        {
            return this.Items.Contains(item);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="array"></param>
        /// <param name="arrayIndex"></param>
        public void CopyTo(T[] array, int arrayIndex)
        {
            this.Items.CopyTo(array, arrayIndex);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public bool Remove(T item)
        {
            bool happened = false;
            ListPreEventArgs<T> beforeEventArgs = new ListPreEventArgs<T>(item);

            OnBeforeItemRemoved(this, beforeEventArgs);
            if (beforeEventArgs.Cancel != true)
            {
                happened = this.Items.Remove(item);
                OnItemRemoved(this, new EventArgs());
            }
            return happened;
        }

        /// <summary>
        /// Remove all values from the list where the predicate is true. foreach is not used as it is not possible
        /// to vary the list elements in a foreach loop.
        /// The events will be called for each item in the list.
        /// </summary>
        /// <param name="condition">predicate to check the list against</param>
        public void RemoveAll(Predicate<T> condition)
        {
            for (int count = _items.Count; count > 0; count--)
            {
                if (condition(_items[count - 1]))
                {
                    RemoveAt(count - 1);
                }
            }
        }

        #endregion

        #region IEnumerable<T>

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public IEnumerator<T> GetEnumerator()
        {
            return this.Items.GetEnumerator();
        }
        #endregion

        #region IEnumerable
        
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }
        #endregion

        #region Events
        
        /// <summary>
        /// Raises when an Item is added to the list.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e">GenericItemEventArgs</param>
        protected virtual void OnItemAdded(object sender, ListEventArgs<T> e)
        {
            if (!_disableEvents && ItemAdded != null)
                ItemAdded(sender, e);
        }
        
        /// <summary>
        /// Raises before an Item is added to the list.
        /// </summary>
        /// <param name="sender">object</param>
        /// <param name="e">GenericItemEventArgs</param>
        protected virtual void OnBeforeItemAdded(object sender, ListPreEventArgs<T> e)
        {
            if (!_disableEvents && BeforeItemAdded != null)
                BeforeItemAdded(sender, e);
        }

        /// <summary>
        /// Raises before an Item is added to the list.
        /// </summary>
        /// <param name="sender">object</param>
        /// <param name="e">GenericItemEventArgs</param>
        protected virtual void OnBeforeItemsAdded(object sender, ListPreEventArgs<T> e)
        {
            if (!_disableEvents && BeforeItemsAdded != null)
                BeforeItemsAdded(sender, e);
        }

        /// <summary>
        /// Raises when an Item is removed from the list.
        /// </summary>
        /// <param name="sender">object</param>
        /// <param name="e">EventsArgs</param>
        protected virtual void OnItemRemoved(object sender, EventArgs e)
        {
            if (!_disableEvents && ItemRemoved != null)
                ItemRemoved(sender, e);
        }

        /// <summary>
        /// Raises before an Item is removed from the list.
        /// </summary>
        /// <param name="sender">object</param>
        /// <param name="e">GenericItemEventArgs</param>
        protected virtual void OnBeforeItemRemoved(object sender, ListPreEventArgs<T> e)
        {
            if (!_disableEvents && BeforeItemRemoved != null)
                BeforeItemRemoved(sender, e);
        }
        
        /// <summary>
        /// Raises when the Items are cleared from this list.
        /// </summary>
        /// <param name="sender">object</param>
        /// <param name="e">EventArgs</param>
        protected virtual void OnItemsCleared(object sender, EventArgs e)
        {
            if (!_disableEvents && ItemsCleared != null)
                ItemsCleared(sender, e);
        }

        /// <summary>
        /// Raises when a collection of items have been added to the list.
        /// </summary>
        /// <param name="sender">object</param>
        /// <param name="e">EventArgs</param>
        protected virtual void OnItemsAdded(object sender, EventArgs e)
        {
            if (!_disableEvents && ItemsAdded != null)
            {
                ItemsAdded(sender, e);
            }
        }
        #endregion

        #region IList Interface Implementation

        /// <summary>
        /// 
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        int IList.Add(object value)
        {
            _items.Add((T)value);

            return _items.IndexOf((T)value);
        }

        /// <summary>
        /// 
        /// </summary>
        void IList.Clear()
        {
            _items.Clear();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        bool IList.Contains(object value)
        {
            return _items.Contains((T)value);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        int IList.IndexOf(object value)
        {
            return _items.IndexOf((T)value);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="index"></param>
        /// <param name="value"></param>
        void IList.Insert(int index, object value)
        {
            _items.Insert(index, (T)value);
        }

        /// <summary>
        /// 
        /// </summary>
        bool IList.IsFixedSize
        {
            get { throw new NotImplementedException(); }
        }

        /// <summary>
        /// 
        /// </summary>
        bool IList.IsReadOnly
        {
            get { return false; }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="value"></param>
        void IList.Remove(object value)
        {
            _items.Remove((T)value);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="index"></param>
        void IList.RemoveAt(int index)
        {
            _items.RemoveAt(index);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        object IList.this[int index]
        {
            get
            {
                return _items[index];
            }
            set
            {
                _items[index] = (T)value;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="array"></param>
        /// <param name="index"></param>
        void ICollection.CopyTo(Array array, int index)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// 
        /// </summary>
        int ICollection.Count
        {
            get { return _items.Count; }
        }

        /// <summary>
        /// 
        /// </summary>
        bool ICollection.IsSynchronized
        {
            get { throw new NotImplementedException(); }
        }

        /// <summary>
        /// 
        /// </summary>
        object ICollection.SyncRoot
        {
            get { throw new NotImplementedException(); }
        }
        #endregion
    }
}
